/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <ppltasks.h>
#include <concurrent_queue.h>
#include <baja/model/generic_model.h>
#include <baja/transition_tracking/tracked_object.h>
#include <baja/transition_tracking/transition_tracking_interfaces.h>
#include <baja/transition_tracking/transition_tracking_helpers.h>
#include <journal/models/interfaces.h>
#include <journal/models/internal_interfaces.h>
#include <journal/models/photo_model.h>

namespace journal { namespace model {

namespace b = baja;
namespace bm = baja::model;
namespace bt = baja::transition_tracking;

class page_model :
    public bm::igeneric_model_observer,
    public bt::tracked_object,
    public ipage_model,
    public iinternal_page_model,
    public iphoto_model_observer,
    public iink_surface_model_observer,
    public std::enable_shared_from_this<ipage_model>
{
public:
    // constuctor
    page_model(
        const b::uint32& typeToken,
        const std::shared_ptr<inotebook_model>& parent,
        const std::shared_ptr<journal::model::db::ipage_table>& pageTable,
        const b::uint32& pageId
        );
    static std::shared_ptr<page_model> create(
        const b::uint32& typeToken,
        const std::shared_ptr<inotebook_model>& parent, 
        const std::shared_ptr<journal::model::db::ipage_table>& pageTable,
        const b::uint32& pageId);

    // ipage_model

    b::int32 addObserver(const std::shared_ptr<ipage_model_observer>& observer);
    void removeObserver(const std::shared_ptr<ipage_model_observer>& observer);
    void removeObserver(const b::int32 cookie);

    b::uint32 storageId();
    b::uint32 pageNumber();
    std::shared_ptr<inotebook_model> parent();

    baja::uint32 paperType();
    void setPaperType(b::uint32 paperType);

    void addPhoto(
        const std::shared_ptr<b::storage::istorage_file>& file,
        const b::float32& scale,
        const m::point2d& position,
        const b::float32& angle
        );

    b::uint32 photoCount();
    void removePhoto(const id& id);

    void bringPhotoToFront(const id& id);

    std::shared_ptr<iink_surface_model> inkSurface();

    void setLoadLevelAsync(
        const page_level targetLevel);

    page_level loadLevel();

    void thumbnail(
        s::binary_blob& outBlob);

    void saveThumbnailAsync(
        s::binary_blob& inBlob);

    void setThumbnailSnapshotPoint();

    b::Boolean isThumbnailUpToDate();

    b::Boolean hasLinkedData();

    DEBUG_MODEL_DECLARATION;

    // iinternal_page_model

    b::int32 addInternalObserver(const std::shared_ptr<iinternal_page_model_observer>& observer);
    void removeInternalObserver(const b::int32 cookie);
    void initialize(const b::uint32& pagePaperType);
    void load(
        const b::uint32 pass,
        const page_level targetLevel);
    void stopLoading();
    void unload();
    void dispose();

    void createInDB();
    void removeFromDB();

    // iphoto_model_observer

    void onPhotoMoved(
        const std::shared_ptr<model::iphoto_model>& photo,
        const b::float32& scale,
        const m::point2d& position,
        const b::float32& angle
        );

    // iink_surface_model_observer

    void onInkSurfaceModelReady(
        const std::shared_ptr<iink_surface_model>& inkSurface);
    void onStrokeModelAttached(
        const std::shared_ptr<iink_stroke_model>& newStroke);
    void onStrokeModelReady(
        const std::shared_ptr<iink_stroke_model>& newStroke);
    void onStrokeModelRemoved(
        const std::shared_ptr<iink_stroke_model>& stroke);
    void onInkSurfaceChildrenReady(
            const std::shared_ptr<iink_surface_model>& inkSurface);

    // igeneric_model_observer
    void onChildChanged(
        const b::uint32& typeToken,
        const b::uint32& genericIdentity
        );

    // destructor
    ~page_model();

private:
    struct observed_ink_surface
    {
        b::uint32 inkSurfaceCookie;
        b::uint32 genericInkSurfaceCookie;

        observed_ink_surface() :
            inkSurfaceCookie(-1),
            genericInkSurfaceCookie(-1)
        {}
    };

    struct observed_photo
    {
        b::uint32 photoCookie;
        b::uint32 genericPhotoCoockie;

        observed_photo() :
            photoCookie(-1),
            genericPhotoCoockie(-1)
        {}
    };

    struct observed_objects
    {
        observed_ink_surface inkSurface;
        std::map<b::uint32, observed_photo> photos;
    };

    observed_objects _observedObjects;

    // state
    b::Boolean _inDB;
    b::Boolean _initialized;
    b::Boolean _disposed;
    std::recursive_mutex _mutex;

    page_level _currentLoadLevel;
    page_level _lastRequestLoadLevel;

    // persistence support
    std::shared_ptr<journal::model::db::ipage_table> _pageTable;

    // persistence support for children
    static const baja::uint32 _inkSurfaceCreatePass;
    bool _inkSurfaceIdReady;
    static const baja::uint32 _photosCreatePass;
    static const baja::uint32 _photosLoadPass;
    bool _photosIdsReady;

    // tree data structure support
    std::weak_ptr<inotebook_model> _parent;

    std::shared_ptr<journal::model::iink_surface_model_factory> _inkSurfaceFactory;
    b::uint32 _inkSurfaceId;
    b::int32 _inkSurfaceObserverCookie;
    std::shared_ptr<iink_surface_model> _inkSurface;

    std::shared_ptr<journal::model::iphoto_model_factory> _photoModelFactory;
    concurrency::concurrent_vector<std::shared_ptr<iphoto_model>> _photos;
    std::vector<b::uint32> _photoIds;

    // attributes
    b::Boolean _linkedData;
    b::uint32 _pagePaperType;
    b::Boolean _thumbnailUpToDate;
    s::binary_blob _thumbnail;

    b::uint64 _lastChangeTick;
    b::uint64 _thumbnailSnapshotTick;
    b::uint64 _savedThumbnailSnapshotTick;

    // observers
    b::observable_source<ipage_model_observer> _observerSource;
    b::observable_source<iinternal_page_model_observer> _internalObserverSource;

    // constuctor
    page_model(
        const b::uint32& typeToken,
        const std::shared_ptr<inotebook_model>& parent,
        const std::shared_ptr<journal::model::db::ipage_table>& pageTable,
        const b::uint32& pageId,
        const b::uint32& pagePaperType
        );

    // private methods
    void observeInkSurface(
        const std::shared_ptr<iink_surface_model>& inkSurface);

    void unobserveInkSurface(
        const std::shared_ptr<iink_surface_model>& inkSurface);

    void observePhoto(
        const std::shared_ptr<iphoto_model>& photo);

    void unobservePhoto(
        const std::shared_ptr<iphoto_model>& photo);

    inline b::Boolean isAccessible();

    concurrency::concurrent_vector<std::shared_ptr<iphoto_model>>::iterator findPhoto(const id& id);

    std::shared_ptr<iphoto_model> createPhotoModel();

    std::shared_ptr<iphoto_model> recreatePhoto(
        const b::uint32& photoId
        );

    void initializePhotoModelAsync(
        const std::shared_ptr<iphoto_model>& photo,
        const std::shared_ptr<b::storage::istorage_file>& file,
        const b::float32& scale,
        const m::point2d& position,
        const b::float32& angle,
        const std::function<void()> initializeCompleted);

    void attachPhotoModel(
        const std::shared_ptr<iphoto_model>& photo);

    void detachPhotoModel(
        const id& id,
        std::shared_ptr<iphoto_model>& photoToRemove,
        b::uint32& photoToRemoveIndex);

    void loadSelf();
    void loadChildrenLinks();

    void loadThumbnail();
    void unloadThumbnail();

    void recreateInkSurface();
    void uncreateInkSurface();

    void loadInkSurface(
        b::uint32 pass,
        bool& done);
    
    void unloadInkSurface();

    void loadPhotos(
        const b::uint32& pass,
        bool& allDone);

    void unloadPhotos();

    void loadFull(
        b::uint32 pass,
        bool& done);

    void unloadFull();

    void saveThumbnail(
        std::shared_ptr<s::binary_blob> blobToSave,
        b::Boolean thumbnailUpToDate);

    void onChanged();
    void onSelfChanged();

    // undo/redo add photo

    enum intermediate_state
    {
        none,
        photoRecreated,
        photoDetached
    };

    intermediate_state _intermediateState;
    std::shared_ptr<iphoto_model> _intermediateDetachedPhoto;
    std::shared_ptr<iphoto_model> _intermediateRecreatedPhoto;

    void undoSelfAction(
        const std::shared_ptr<bt::itransition_parameters>& transitionParameters);

    void redoSelfAction(
        const std::shared_ptr<bt::itransition_parameters>& transitionParameters);

    void undoChildAction(
        const std::vector<bt::transition_path_node>& path,
        const b::uint32& childIndex,
        const std::shared_ptr<bt::itransition_parameters>& transitionParameters);

    void redoChildAction(
        const std::vector<bt::transition_path_node>& path,
        const b::uint32& childIndex,
        const std::shared_ptr<bt::itransition_parameters>& transitionParameters);

    void recreatePhoto(
        const std::shared_ptr<bt::generic_transition_parameters>& p);

    void reattachPhoto(
        const std::shared_ptr<bt::generic_transition_parameters>& p);

    void redetachPhoto(
        const std::shared_ptr<bt::generic_transition_parameters>& p);

    void redeletePhoto(
        const std::shared_ptr<bt::generic_transition_parameters>& p);

};

std::shared_ptr<ipage_model> createPageModel(const b::uint32& typeToken,
                                             const std::shared_ptr<inotebook_model>& parent,
                                             const std::shared_ptr<journal::model::db::ipage_table>& pageTable,
                                             const b::uint32& pageId);

}} // namespace journal::model
